Machine Learning review and intro to tidymodels
- Read about the hotel booking data,
hotels, on the Tidy Tuesday page it came from. There is also a link to an article from the original authors. The outcome we will be predicting is called is_canceled.
- Without doing any analysis, what are some variables you think might be predictive and why?
- There are a few variables that could be predictive, however
previous_cancellations definitely stands out. It is reasonable to assume that if someone has canceled before, they could possiblky cancel again. booking_changes could also be predictive, as someone who makes a bunch of changes is likely to be unsure about their booking and one of the changes that they could make could be canceling the stay. Finally, a third variable that could be predictive is customer_type, as the type of customer could make it easier or more difficult to cancel the stay.
- What are some problems that might exist with the data? You might think about how it was collected and who did the collecting.
- One issue with the way that the data was collected is that there are almost twice as many observations for the city hotel as the resort hotel, which could introduce bias into the data. The data for canceled bookings could also be less accurate, as variables such as adults, children, and babies could be inaccurate due to the family or group never showing up. Therefore, there could be bias towards non-canceled bookings based on how the data was collected. The
reservation_status variable is also a bit redundant, as it states whether someone canceled their reservation or not, which is already given in the is_canceled variable. Finally, each reservation is missing a unique identifier, so
- If we construct a model, what type of conclusions will be able to draw from it?
- If we construct a model, the type of conclusions that we’ll be able to draw from it are likely to be which variables are most important to determine the likelihood of cancellation. This could easily be achieved using the LASSO technique to analyze variable importance.
- Create some exploratory plots or table summaries of the data, concentrating most on relationships with the response variable. Keep in mind the response variable is numeric, 0 or 1. You may want to make it categorical (you also may not). Be sure to also examine missing values or other interesting values.
hotels %>%
ggplot(aes(x = hotel)) +
geom_bar(fill = "blue") +
facet_wrap(vars(is_canceled))

hotels %>%
ggplot(aes(x = customer_type)) +
geom_bar(fill = "red") +
facet_wrap(vars(is_canceled))

hotels %>%
ggplot(aes(x = previous_cancellations)) +
geom_bar(fill = "orange") +
xlim(0,3) +
ylim(0,7000) +
facet_wrap(vars(is_canceled))

hotels %>%
ggplot(aes(x = adults)) +
geom_bar(fill = "green") +
xlim(0,5) +
facet_wrap(vars(is_canceled))

hotels %>%
ggplot(aes(x = children)) +
geom_bar(fill = "green") +
xlim(0,5) +
ylim(0,2500) +
facet_wrap(vars(is_canceled))

hotels %>%
ggplot(aes(x = babies)) +
geom_bar(fill = "green") +
xlim(0,5) +
ylim(0,200) +
facet_wrap(vars(is_canceled))

- First, we will do a couple things to get the data ready, including making the outcome a factor (needs to be that way for logistic regression), removing the year variable and some reservation status variables, and removing missing values (not NULLs but true missing values). Split the data into a training and test set, stratifying on the outcome variable,
is_canceled. Since we have a lot of data, we’re going to split the data 50/50 between training and test. I have already set.seed() for you. Be sure to use hotels_mod in the splitting.
hotels_mod <- hotels %>%
mutate(is_canceled = as.factor(is_canceled)) %>%
mutate(across(where(is.character), as.factor)) %>%
select(-arrival_date_year,
-reservation_status,
-reservation_status_date) %>%
add_n_miss() %>%
filter(n_miss_all == 0) %>%
select(-n_miss_all)
set.seed(494)
hotel_split <- initial_split(hotels_mod,
prop = 0.5)
hotel_train <- training(hotel_split)
hotel_test <- testing(hotel_split)
- Pre-processing
hotel_recipe <- recipe(is_canceled ~.,
data = hotel_train) %>%
step_mutate_at(children, babies, previous_cancellations,
fn = ~ifelse(. > 0, 1, 0)) %>%
step_mutate_at(agent, company,
fn = ~ifelse(. == "NULL", 1, 0)) %>%
step_mutate(country = fct_lump_n(country, 5)) %>%
step_normalize(all_numeric()) %>%
step_dummy(all_nominal(), -all_outcomes())
hotel_recipe %>%
prep(hotel_train) %>%
juice()
- LASSO model and workflow
- We would want to use a LASSO workflow because LASSO uses an importance coefficient which reduces to zero when the variable is deemed to be not predictive of our outcome variable. This will allow us to reduce the size of our dataset and only focus on our indicator variables that matter.
lasso_hotel_mod <- logistic_reg(penalty = tune()) %>%
set_engine("glmnet") %>%
set_mode("classification")
hotel_workflow <- workflow() %>%
add_recipe(hotel_recipe) %>%
add_model(lasso_hotel_mod)
lasso_hotel_fit <- hotel_workflow %>%
fit(data = hotel_train)
lasso_hotel_fit
## ══ Workflow [trained] ══════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
##
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 5 Recipe Steps
##
## ● step_mutate_at()
## ● step_mutate_at()
## ● step_mutate()
## ● step_normalize()
## ● step_dummy()
##
## ── Model ───────────────────────────────────────────────────────────────────────
##
## Call: glmnet::glmnet(x = maybe_matrix(x), y = y, family = "binomial")
##
## Df %Dev Lambda
## 1 0 0.00 0.232900
## 2 1 2.92 0.212200
## 3 1 5.29 0.193300
## 4 1 7.23 0.176200
## 5 1 8.86 0.160500
## 6 1 10.24 0.146300
## 7 1 11.42 0.133300
## 8 1 12.43 0.121400
## 9 2 13.34 0.110600
## 10 2 14.38 0.100800
## 11 2 15.29 0.091850
## 12 3 16.24 0.083690
## 13 5 17.38 0.076260
## 14 5 18.58 0.069480
## 15 7 19.68 0.063310
## 16 7 21.76 0.057690
## 17 7 23.59 0.052560
## 18 7 25.20 0.047890
## 19 8 26.66 0.043640
## 20 9 27.97 0.039760
## 21 10 29.26 0.036230
## 22 10 30.40 0.033010
## 23 11 31.43 0.030080
## 24 12 32.37 0.027410
## 25 13 33.21 0.024970
## 26 14 33.99 0.022750
## 27 14 34.68 0.020730
## 28 15 35.29 0.018890
## 29 18 35.86 0.017210
## 30 19 36.41 0.015680
## 31 21 36.90 0.014290
## 32 22 37.34 0.013020
## 33 23 37.74 0.011860
## 34 23 38.09 0.010810
## 35 25 38.40 0.009849
## 36 29 38.74 0.008974
## 37 31 39.07 0.008177
## 38 32 39.37 0.007451
## 39 34 39.66 0.006789
## 40 36 39.93 0.006186
## 41 40 40.18 0.005636
## 42 41 40.42 0.005135
## 43 42 40.63 0.004679
## 44 43 40.82 0.004263
## 45 44 41.00 0.003885
## 46 44 41.17 0.003540
##
## ...
## and 40 more lines.
- Tune and fit the model
set.seed(494)
hotel_cv <- vfold_cv(hotel_train, v = 5)
hotel_lasso_pen_grid <- grid_regular(penalty(), levels = 10)
hotel_lasso_tune <- hotel_workflow %>%
tune_grid(resamples = hotel_cv,
grid = hotel_lasso_pen_grid)
hotel_lasso_tune %>%
collect_metrics() %>%
filter(.metric == "accuracy")
hotel_lasso_tune %>%
collect_metrics() %>%
filter(.metric == "accuracy") %>%
ggplot(aes(x = penalty, y = mean)) +
geom_point() +
geom_line() +
scale_x_log10(
breaks = scales::trans_breaks("log10", function(x) 10^x),
labels = scales::trans_format("log10",scales::math_format(10^.x))) +
labs(x = "Penalty",
y = "Accuracy")

best_param <- hotel_lasso_tune %>%
select_best(metric = "accuracy")
hotel_lasso_final_workflow <- hotel_workflow %>%
finalize_workflow(best_param)
hotel_lasso_final_mod <- hotel_lasso_final_workflow %>%
fit(data = hotel_train)
hotel_lasso_final_mod %>%
pull_workflow_fit() %>%
tidy()
- Some of the coefficients are zero, signifying that those predictor variables are not a factor in our LASSO model.
- Variable Importance Graph
hotel_lasso_final_mod %>%
pull_workflow_fit() %>%
vip()

- It is unsurprising that the type of room reserved is the most important variable. It can explain a possible hypothesis that the majority of cancellations come when someone in a group drops out of a reservation. The importance of the non-refundable deposit type is also unsurprising because putting down a non-refundable deposit will most likely influence someone to not cancel their reservation.
hotel_lasso_test <- hotel_lasso_final_workflow %>%
last_fit(hotel_split)
hotel_lasso_test %>%
collect_metrics()
- The accuracy estimate for the test data is slightly higher than for the cross-validated data.
preds <- collect_predictions(hotel_lasso_test)
preds %>%
conf_mat(.pred_class, is_canceled)
## Truth
## Prediction 0 1
## 0 34492 3165
## 1 8004 14032
- Sensitivity: \(\frac{34492}{34492+8004} = 0.81165\)
- Specificity: \(\frac{14032}{14032 + 3165} = 0.815956\)
preds %>%
ggplot(aes(x = .pred_1, fill = is_canceled)) +
geom_density(alpha = 0.5, color = NA)

- For an accuracy close to 1, the plot would have almost no overlap at all, as the true positive and true negative rates would each be close to 1.
- If we want to have a higher true positive rate, we should make the cutoff higher than 0.5, as only including predictions that are more likely to be canceled would filter out predicitions that were somewhat likely (i.e. 50%-60% likely) to be canceled that ended up not being canceled.
- If the true positive rate increases, the true negative rate should increase as well, because by increasing the true positive rate, we are eliminating incorrect predictions. Eliminating incorrect predictions will drive down the false negative rate.
- Let’s say that this model is going to be applied to bookings 14 days in advance of their arrival at each hotel, and someone who works for the hotel will make a phone call to the person who made the booking. During this phone call, they will try to assure that the person will be keeping their reservation or that they will be canceling in which case they can do that now and still have time to fill the room. How should the hotel go about deciding who to call? How could they measure whether it was worth the effort to do the calling? Can you think of another way they might use the model?
- The hotel should first call people who are in room type P, as room type P is the most important variable when it comes to predicting whether a reservation will be canceled or not. The hotel should also look at the type of deposit that the reservation maker put down, as that is also very predictive of whether the reservation will be canceled or not. The way that they could measure whether the calls were worth it or not could be by looking at how many cancellations were discovered based on the calls versus the total number of cancellations. They could also observe how many reservations were made for the rooms after they were canceled as a result of the calls. Another way that they could use the model is by looking at the rooms that are canceled the most and making deposits on those rooms non-refundable.
- How might you go about questioning and evaluating the model in terms of fairness? Are there any questions you would like to ask of the people who collected the data?
- In terms of fairness, we might want to discuss the potential issues with who we are evaluating. With regards to the individual, it is possible that we are only looking at wealthy people who can afford to go on vacation and stay at a hotel or a resort. With regards to the group reservation type, it is possible that the majority are either multiple wealthy friends, or families who are able to afford going on vacation. This is a situation where demographics are important, and I would want to ask the researchers about the demographics of the people who were making the reservations, as by including mostly wealthy people, the researchers would not be understanding the true trends of the population.
LS0tCnRpdGxlOiAnQXNzaWdubWVudCAjMScKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UpCmBgYAoKYGBge3IgbGlicmFyaWVzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICAgICAgICAjIGZvciBncmFwaGluZyBhbmQgZGF0YSBjbGVhbmluZwpsaWJyYXJ5KHRpZHltb2RlbHMpICAgICAgICAjIGZvciBtb2RlbGluZwpsaWJyYXJ5KG5hbmlhcikgICAgICAgICAgICAjIGZvciBhbmFseXppbmcgbWlzc2luZyB2YWx1ZXMKbGlicmFyeSh2aXApICAgICAgICAgICAgICAgIyBmb3IgdmFyaWFibGUgaW1wb3J0YW5jZSBwbG90cwpsaWJyYXJ5KGdsbW5ldCkgICAgICAgICAgICAjIGZvciByZWd1bGFyaXplZCByZWdyZXNzaW9uLCBpbmNsdWRpbmcgTEFTU08KYGBgCgoKYGBge3IgZGF0YSwgY2FjaGU9VFJVRSwgbWVzc2FnZT1GQUxTRX0KaG90ZWxzIDwtIHJlYWRyOjpyZWFkX2NzdignaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9tYXN0ZXIvZGF0YS8yMDIwLzIwMjAtMDItMTEvaG90ZWxzLmNzdicpCmBgYAoKCiMjIFNldHRpbmcgVXAgR2l0IGFuZCBHaXRodWIgaW4gUlN0dWRpbwoKW0hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9hbGV4ZGVuemxlci9TVEFUNDk0X3NpdGVfRGVuemxlcikgaXMgbXkgR2l0aHViIGxpbmsuCgoKIyMgQ3JlYXRpbmcgYSBXZWJzaXRlCgoqIFdlYnNpdGUgTGluawogICsgW0hlcmVdKGh0dHBzOi8vdXBiZWF0LWhhd2tpbmctYjllZjI2Lm5ldGxpZnkuYXBwKSBpcyB0aGUgbGluayB0byBteSB3ZWJzaXRlLgogIAoqIEJ1aWxkaW5nIGEgQ2FyZWVyIGluIERhdGEgU2NpZW5jZSwgQ2hhcHRlciA0OiBCdWlsZGluZyBhIFBvcnRmb2xpbyByZWZsZWN0aW9uCiAgKyBPbmUgdGhpbmcgdGhhdCBzdG9vZCBvdXQgbW9zdCB0byBtZSB3YXMgdGhlIGNvbW1lbnQgYWJvdXQgbWFraW5nIGEgcG9ydGZvbGlvIHdpdGggcmVzdWx0cyB0aGF0IHJlZ3VsYXIgcGVvcGxlIGNhbiBkaWdlc3QuIEkgYmVsaWV2ZSB0aGF0IHRoaXMgaXMgaW1wb3J0YW50IGZvciBtdWx0aXBsZSByZWFzb25zLiBGaXJzdCwgbWFraW5nIHN1cmUgdGhhdCB5b3VyIHBvcnRmb2xpbyBpcyB1bmRlcnN0YW5kYWJsZSB3aWxsIGhlbHAgcmVjcnVpdGVycyB3aG8gYXJlIG5vdCBuZWNlc3NhcmlseSB2ZXJzZWQgaW4gZGF0YSBzY2llbmNlIHRlY2huaXF1ZXMgdW5kZXJzdGFuZCB5b3VyIHdvcmsgYW5kIHlvdXIgYWJpbGl0aWVzLiBTZWNvbmQsIGl0IGlzIG1vc3QgbGlrZWx5IHRoYXQgd2hlbiB3b3JraW5nIGluIHRoZSBwcm9mZXNzaW9uYWwgd29ybGQsIHlvdSB3aWxsIG5lZWQgdG8gZXhwbGFpbiB5b3VyIHJlc3VsdHMgdG8gcGVvcGxlIHdobyBhcmUgbm90IG9uIHRoZSBkYXRhIHNjaWVuY2UgdGVhbSwgc28gaXQgaXMgZ29vZCB0byBwcmFjdGljZSB0aGlzIGFiaWxpdHkuCgoKIyMgTWFjaGluZSBMZWFybmluZyByZXZpZXcgYW5kIGludHJvIHRvIGB0aWR5bW9kZWxzYAoKKEApIFJlYWQgYWJvdXQgdGhlIGhvdGVsIGJvb2tpbmcgZGF0YSwgYGhvdGVsc2AsIG9uIHRoZSBUaWR5IFR1ZXNkYXkgcGFnZSBpdCBjYW1lIGZyb20uIFRoZXJlIGlzIGFsc28gYSBsaW5rIHRvIGFuIGFydGljbGUgZnJvbSB0aGUgb3JpZ2luYWwgYXV0aG9ycy4gVGhlIG91dGNvbWUgd2Ugd2lsbCBiZSBwcmVkaWN0aW5nIGlzIGNhbGxlZCBgaXNfY2FuY2VsZWRgLgoKKiBXaXRob3V0IGRvaW5nIGFueSBhbmFseXNpcywgd2hhdCBhcmUgc29tZSB2YXJpYWJsZXMgeW91IHRoaW5rIG1pZ2h0IGJlIHByZWRpY3RpdmUgYW5kIHdoeT8KICArIFRoZXJlIGFyZSBhIGZldyB2YXJpYWJsZXMgdGhhdCBjb3VsZCBiZSBwcmVkaWN0aXZlLCBob3dldmVyIGBwcmV2aW91c19jYW5jZWxsYXRpb25zYCBkZWZpbml0ZWx5IHN0YW5kcyBvdXQuIEl0IGlzIHJlYXNvbmFibGUgdG8gYXNzdW1lIHRoYXQgaWYgc29tZW9uZSBoYXMgY2FuY2VsZWQgYmVmb3JlLCB0aGV5IGNvdWxkIHBvc3NpYmxreSBjYW5jZWwgYWdhaW4uIGBib29raW5nX2NoYW5nZXNgIGNvdWxkIGFsc28gYmUgcHJlZGljdGl2ZSwgYXMgc29tZW9uZSB3aG8gbWFrZXMgYSBidW5jaCBvZiBjaGFuZ2VzIGlzIGxpa2VseSB0byBiZSB1bnN1cmUgYWJvdXQgdGhlaXIgYm9va2luZyBhbmQgb25lIG9mIHRoZSBjaGFuZ2VzIHRoYXQgdGhleSBjb3VsZCBtYWtlIGNvdWxkIGJlIGNhbmNlbGluZyB0aGUgc3RheS4gRmluYWxseSwgYSB0aGlyZCB2YXJpYWJsZSB0aGF0IGNvdWxkIGJlIHByZWRpY3RpdmUgaXMgYGN1c3RvbWVyX3R5cGVgLCBhcyB0aGUgdHlwZSBvZiBjdXN0b21lciBjb3VsZCBtYWtlIGl0IGVhc2llciBvciBtb3JlIGRpZmZpY3VsdCB0byBjYW5jZWwgdGhlIHN0YXkuIAogIAoqIFdoYXQgYXJlIHNvbWUgcHJvYmxlbXMgdGhhdCBtaWdodCBleGlzdCB3aXRoIHRoZSBkYXRhPyBZb3UgbWlnaHQgdGhpbmsgYWJvdXQgaG93IGl0IHdhcyBjb2xsZWN0ZWQgYW5kIHdobyBkaWQgdGhlIGNvbGxlY3RpbmcuCiAgKyBPbmUgaXNzdWUgd2l0aCB0aGUgd2F5IHRoYXQgdGhlIGRhdGEgd2FzIGNvbGxlY3RlZCBpcyB0aGF0IHRoZXJlIGFyZSBhbG1vc3QgdHdpY2UgYXMgbWFueSBvYnNlcnZhdGlvbnMgZm9yIHRoZSBjaXR5IGhvdGVsIGFzIHRoZSByZXNvcnQgaG90ZWwsIHdoaWNoIGNvdWxkIGludHJvZHVjZSBiaWFzIGludG8gdGhlIGRhdGEuIFRoZSBkYXRhIGZvciBjYW5jZWxlZCBib29raW5ncyBjb3VsZCBhbHNvIGJlIGxlc3MgYWNjdXJhdGUsIGFzIHZhcmlhYmxlcyBzdWNoIGFzIGFkdWx0cywgY2hpbGRyZW4sIGFuZCBiYWJpZXMgY291bGQgYmUgaW5hY2N1cmF0ZSBkdWUgdG8gdGhlIGZhbWlseSBvciBncm91cCBuZXZlciBzaG93aW5nIHVwLiBUaGVyZWZvcmUsIHRoZXJlIGNvdWxkIGJlIGJpYXMgdG93YXJkcyBub24tY2FuY2VsZWQgYm9va2luZ3MgYmFzZWQgb24gaG93IHRoZSBkYXRhIHdhcyBjb2xsZWN0ZWQuIFRoZSBgcmVzZXJ2YXRpb25fc3RhdHVzYCB2YXJpYWJsZSBpcyBhbHNvIGEgYml0IHJlZHVuZGFudCwgYXMgaXQgc3RhdGVzIHdoZXRoZXIgc29tZW9uZSBjYW5jZWxlZCB0aGVpciByZXNlcnZhdGlvbiBvciBub3QsIHdoaWNoIGlzIGFscmVhZHkgZ2l2ZW4gaW4gdGhlIGBpc19jYW5jZWxlZGAgdmFyaWFibGUuIEZpbmFsbHksIGVhY2ggcmVzZXJ2YXRpb24gaXMgbWlzc2luZyBhIHVuaXF1ZSBpZGVudGlmaWVyLCBzbyAKICAKKiBJZiB3ZSBjb25zdHJ1Y3QgYSBtb2RlbCwgd2hhdCB0eXBlIG9mIGNvbmNsdXNpb25zIHdpbGwgYmUgYWJsZSB0byBkcmF3IGZyb20gaXQ/CiAgKyBJZiB3ZSBjb25zdHJ1Y3QgYSBtb2RlbCwgdGhlIHR5cGUgb2YgY29uY2x1c2lvbnMgdGhhdCB3ZSdsbCBiZSBhYmxlIHRvIGRyYXcgZnJvbSBpdCBhcmUgbGlrZWx5IHRvIGJlIHdoaWNoIHZhcmlhYmxlcyBhcmUgbW9zdCBpbXBvcnRhbnQgdG8gZGV0ZXJtaW5lIHRoZSBsaWtlbGlob29kIG9mIGNhbmNlbGxhdGlvbi4gVGhpcyBjb3VsZCBlYXNpbHkgYmUgYWNoaWV2ZWQgdXNpbmcgdGhlIExBU1NPIHRlY2huaXF1ZSB0byBhbmFseXplIHZhcmlhYmxlIGltcG9ydGFuY2UuCiAgCgooQCkgQ3JlYXRlIHNvbWUgZXhwbG9yYXRvcnkgcGxvdHMgb3IgdGFibGUgc3VtbWFyaWVzIG9mIHRoZSBkYXRhLCBjb25jZW50cmF0aW5nIG1vc3Qgb24gcmVsYXRpb25zaGlwcyB3aXRoIHRoZSByZXNwb25zZSB2YXJpYWJsZS4gS2VlcCBpbiBtaW5kIHRoZSByZXNwb25zZSB2YXJpYWJsZSBpcyBudW1lcmljLCAwIG9yIDEuIFlvdSBtYXkgd2FudCB0byBtYWtlIGl0IGNhdGVnb3JpY2FsICh5b3UgYWxzbyBtYXkgbm90KS4gQmUgc3VyZSB0byBhbHNvIGV4YW1pbmUgbWlzc2luZyB2YWx1ZXMgb3Igb3RoZXIgaW50ZXJlc3RpbmcgdmFsdWVzLgoKYGBge3J9CmhvdGVscyAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gaG90ZWwpKSArIAogIGdlb21fYmFyKGZpbGwgPSAiYmx1ZSIpICsgCiAgZmFjZXRfd3JhcCh2YXJzKGlzX2NhbmNlbGVkKSkKYGBgCmBgYHtyfQpob3RlbHMgJT4lIAogIGdncGxvdChhZXMoeCA9IGN1c3RvbWVyX3R5cGUpKSArIAogIGdlb21fYmFyKGZpbGwgPSAicmVkIikgKwogIGZhY2V0X3dyYXAodmFycyhpc19jYW5jZWxlZCkpCmBgYAoKYGBge3J9CmhvdGVscyAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gcHJldmlvdXNfY2FuY2VsbGF0aW9ucykpICsKICBnZW9tX2JhcihmaWxsID0gIm9yYW5nZSIpICsKICB4bGltKDAsMykgKwogIHlsaW0oMCw3MDAwKSArCiAgZmFjZXRfd3JhcCh2YXJzKGlzX2NhbmNlbGVkKSkKYGBgCgoKYGBge3J9CmhvdGVscyAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gYWR1bHRzKSkgKyAKICBnZW9tX2JhcihmaWxsID0gImdyZWVuIikgKwogIHhsaW0oMCw1KSArCiAgZmFjZXRfd3JhcCh2YXJzKGlzX2NhbmNlbGVkKSkKYGBgCmBgYHtyfQpob3RlbHMgJT4lIAogIGdncGxvdChhZXMoeCA9IGNoaWxkcmVuKSkgKyAKICBnZW9tX2JhcihmaWxsID0gImdyZWVuIikgKwogIHhsaW0oMCw1KSArCiAgeWxpbSgwLDI1MDApICsKICBmYWNldF93cmFwKHZhcnMoaXNfY2FuY2VsZWQpKQpgYGAKCmBgYHtyfQpob3RlbHMgJT4lIAogIGdncGxvdChhZXMoeCA9IGJhYmllcykpICsgCiAgZ2VvbV9iYXIoZmlsbCA9ICJncmVlbiIpICsKICB4bGltKDAsNSkgKwogIHlsaW0oMCwyMDApICsKICBmYWNldF93cmFwKHZhcnMoaXNfY2FuY2VsZWQpKQpgYGAKCgooQCkgRmlyc3QsIHdlIHdpbGwgZG8gYSBjb3VwbGUgdGhpbmdzIHRvIGdldCB0aGUgZGF0YSByZWFkeSwgaW5jbHVkaW5nIG1ha2luZyB0aGUgb3V0Y29tZSBhIGZhY3RvciAobmVlZHMgdG8gYmUgdGhhdCB3YXkgZm9yIGxvZ2lzdGljIHJlZ3Jlc3Npb24pLCByZW1vdmluZyB0aGUgeWVhciB2YXJpYWJsZSBhbmQgc29tZSByZXNlcnZhdGlvbiBzdGF0dXMgdmFyaWFibGVzLCBhbmQgcmVtb3ZpbmcgbWlzc2luZyB2YWx1ZXMgKG5vdCBOVUxMcyBidXQgdHJ1ZSBtaXNzaW5nIHZhbHVlcykuIFNwbGl0IHRoZSBkYXRhIGludG8gYSB0cmFpbmluZyBhbmQgdGVzdCBzZXQsIHN0cmF0aWZ5aW5nIG9uIHRoZSBvdXRjb21lIHZhcmlhYmxlLCBgaXNfY2FuY2VsZWRgLiBTaW5jZSB3ZSBoYXZlIGEgbG90IG9mIGRhdGEsIHdl4oCZcmUgZ29pbmcgdG8gc3BsaXQgdGhlIGRhdGEgNTAvNTAgYmV0d2VlbiB0cmFpbmluZyBhbmQgdGVzdC4gSSBoYXZlIGFscmVhZHkgYHNldC5zZWVkKClgIGZvciB5b3UuIEJlIHN1cmUgdG8gdXNlIGBob3RlbHNfbW9kYCBpbiB0aGUgc3BsaXR0aW5nLgoKYGBge3J9CmhvdGVsc19tb2QgPC0gaG90ZWxzICU+JSAKICBtdXRhdGUoaXNfY2FuY2VsZWQgPSBhcy5mYWN0b3IoaXNfY2FuY2VsZWQpKSAlPiUgCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5jaGFyYWN0ZXIpLCBhcy5mYWN0b3IpKSAlPiUgCiAgc2VsZWN0KC1hcnJpdmFsX2RhdGVfeWVhciwKICAgICAgICAgLXJlc2VydmF0aW9uX3N0YXR1cywKICAgICAgICAgLXJlc2VydmF0aW9uX3N0YXR1c19kYXRlKSAlPiUgCiAgYWRkX25fbWlzcygpICU+JSAKICBmaWx0ZXIobl9taXNzX2FsbCA9PSAwKSAlPiUgCiAgc2VsZWN0KC1uX21pc3NfYWxsKQoKc2V0LnNlZWQoNDk0KQpgYGAKCmBgYHtyfQpob3RlbF9zcGxpdCA8LSBpbml0aWFsX3NwbGl0KGhvdGVsc19tb2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvcCA9IDAuNSkKaG90ZWxfdHJhaW4gPC0gdHJhaW5pbmcoaG90ZWxfc3BsaXQpCmhvdGVsX3Rlc3QgPC0gdGVzdGluZyhob3RlbF9zcGxpdCkKYGBgCgooQCkgUHJlLXByb2Nlc3NpbmcKCmBgYHtyfQpob3RlbF9yZWNpcGUgPC0gcmVjaXBlKGlzX2NhbmNlbGVkIH4uLAogICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBob3RlbF90cmFpbikgJT4lIAogIHN0ZXBfbXV0YXRlX2F0KGNoaWxkcmVuLCBiYWJpZXMsIHByZXZpb3VzX2NhbmNlbGxhdGlvbnMsCiAgICAgICAgICAgICAgICAgZm4gPSB+aWZlbHNlKC4gPiAwLCAxLCAwKSkgJT4lIAogICAgc3RlcF9tdXRhdGVfYXQoYWdlbnQsIGNvbXBhbnksCiAgICAgICAgICAgICAgICAgIGZuID0gfmlmZWxzZSguID09ICJOVUxMIiwgMSwgMCkpICU+JSAKICAgICAgc3RlcF9tdXRhdGUoY291bnRyeSA9IGZjdF9sdW1wX24oY291bnRyeSwgNSkpICU+JSAKICAgICAgICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpYygpKSAlPiUgCiAgICAgICAgICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSkKCmhvdGVsX3JlY2lwZSAlPiUgCiAgcHJlcChob3RlbF90cmFpbikgJT4lIAogIGp1aWNlKCkKYGBgCgoKKEApIExBU1NPIG1vZGVsIGFuZCB3b3JrZmxvdwoqIFdlIHdvdWxkIHdhbnQgdG8gdXNlIGEgTEFTU08gd29ya2Zsb3cgYmVjYXVzZSBMQVNTTyB1c2VzIGFuIGltcG9ydGFuY2UgY29lZmZpY2llbnQgd2hpY2ggcmVkdWNlcyB0byB6ZXJvIHdoZW4gdGhlIHZhcmlhYmxlIGlzIGRlZW1lZCB0byBiZSBub3QgcHJlZGljdGl2ZSBvZiBvdXIgb3V0Y29tZSB2YXJpYWJsZS4gVGhpcyB3aWxsIGFsbG93IHVzIHRvIHJlZHVjZSB0aGUgc2l6ZSBvZiBvdXIgZGF0YXNldCBhbmQgb25seSBmb2N1cyBvbiBvdXIgaW5kaWNhdG9yIHZhcmlhYmxlcyB0aGF0IG1hdHRlci4KCmBgYHtyfQpsYXNzb19ob3RlbF9tb2QgPC0gbG9naXN0aWNfcmVnKHBlbmFsdHkgPSB0dW5lKCkpICU+JQogIHNldF9lbmdpbmUoImdsbW5ldCIpICU+JSAKICAgIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpCgpob3RlbF93b3JrZmxvdyA8LSB3b3JrZmxvdygpICU+JQogIGFkZF9yZWNpcGUoaG90ZWxfcmVjaXBlKSAlPiUKICAgIGFkZF9tb2RlbChsYXNzb19ob3RlbF9tb2QpCgpsYXNzb19ob3RlbF9maXQgPC0gaG90ZWxfd29ya2Zsb3cgJT4lCiAgZml0KGRhdGEgPSBob3RlbF90cmFpbikKCmxhc3NvX2hvdGVsX2ZpdApgYGAKCgooQCkgVHVuZSBhbmQgZml0IHRoZSBtb2RlbAoKYGBge3J9CnNldC5zZWVkKDQ5NCkKCmhvdGVsX2N2IDwtIHZmb2xkX2N2KGhvdGVsX3RyYWluLCB2ID0gNSkKCmhvdGVsX2xhc3NvX3Blbl9ncmlkIDwtIGdyaWRfcmVndWxhcihwZW5hbHR5KCksIGxldmVscyA9IDEwKQoKaG90ZWxfbGFzc29fdHVuZSA8LSBob3RlbF93b3JrZmxvdyAlPiUgCiAgdHVuZV9ncmlkKHJlc2FtcGxlcyA9IGhvdGVsX2N2LAogICAgICAgICAgICBncmlkID0gaG90ZWxfbGFzc29fcGVuX2dyaWQpCmBgYAoKYGBge3J9CmhvdGVsX2xhc3NvX3R1bmUgJT4lIAogIGNvbGxlY3RfbWV0cmljcygpICU+JSAKICAgICAgZmlsdGVyKC5tZXRyaWMgPT0gImFjY3VyYWN5IikKYGBgCgpgYGB7cn0KaG90ZWxfbGFzc29fdHVuZSAlPiUgCiAgY29sbGVjdF9tZXRyaWNzKCkgJT4lIAogICAgICBmaWx0ZXIoLm1ldHJpYyA9PSAiYWNjdXJhY3kiKSAlPiUgCiAgICAgICAgZ2dwbG90KGFlcyh4ID0gcGVuYWx0eSwgeSA9IG1lYW4pKSArCiAgICAgICAgZ2VvbV9wb2ludCgpICsKICAgICAgICBnZW9tX2xpbmUoKSArCiAgICAgICAgc2NhbGVfeF9sb2cxMCgKICAgICAgICBicmVha3MgPSBzY2FsZXM6OnRyYW5zX2JyZWFrcygibG9nMTAiLCBmdW5jdGlvbih4KSAxMF54KSwKICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnRyYW5zX2Zvcm1hdCgibG9nMTAiLHNjYWxlczo6bWF0aF9mb3JtYXQoMTBeLngpKSkgKwogICAgICAgIGxhYnMoeCA9ICJQZW5hbHR5IiwKICAgICAgICAgICAgIHkgPSAiQWNjdXJhY3kiKQpgYGAKCmBgYHtyfQpiZXN0X3BhcmFtIDwtIGhvdGVsX2xhc3NvX3R1bmUgJT4lIAogIHNlbGVjdF9iZXN0KG1ldHJpYyA9ICJhY2N1cmFjeSIpCgpob3RlbF9sYXNzb19maW5hbF93b3JrZmxvdyA8LSBob3RlbF93b3JrZmxvdyAlPiUgCiAgZmluYWxpemVfd29ya2Zsb3coYmVzdF9wYXJhbSkKCmhvdGVsX2xhc3NvX2ZpbmFsX21vZCA8LSBob3RlbF9sYXNzb19maW5hbF93b3JrZmxvdyAlPiUgCiAgZml0KGRhdGEgPSBob3RlbF90cmFpbikKCmhvdGVsX2xhc3NvX2ZpbmFsX21vZCAlPiUgCiAgcHVsbF93b3JrZmxvd19maXQoKSAlPiUgCiAgdGlkeSgpCmBgYAoKKiBTb21lIG9mIHRoZSBjb2VmZmljaWVudHMgYXJlIHplcm8sIHNpZ25pZnlpbmcgdGhhdCB0aG9zZSBwcmVkaWN0b3IgdmFyaWFibGVzIGFyZSBub3QgYSBmYWN0b3IgaW4gb3VyIExBU1NPIG1vZGVsLgoKCihAKSBWYXJpYWJsZSBJbXBvcnRhbmNlIEdyYXBoCgpgYGB7cn0KaG90ZWxfbGFzc29fZmluYWxfbW9kICU+JSAKICBwdWxsX3dvcmtmbG93X2ZpdCgpICU+JSAKICB2aXAoKQpgYGAKCiogSXQgaXMgdW5zdXJwcmlzaW5nIHRoYXQgdGhlIHR5cGUgb2Ygcm9vbSByZXNlcnZlZCBpcyB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGUuIEl0IGNhbiBleHBsYWluIGEgcG9zc2libGUgaHlwb3RoZXNpcyB0aGF0IHRoZSBtYWpvcml0eSBvZiBjYW5jZWxsYXRpb25zIGNvbWUgd2hlbiBzb21lb25lIGluIGEgZ3JvdXAgZHJvcHMgb3V0IG9mIGEgcmVzZXJ2YXRpb24uIFRoZSBpbXBvcnRhbmNlIG9mIHRoZSBub24tcmVmdW5kYWJsZSBkZXBvc2l0IHR5cGUgaXMgYWxzbyB1bnN1cnByaXNpbmcgYmVjYXVzZSBwdXR0aW5nIGRvd24gYSBub24tcmVmdW5kYWJsZSBkZXBvc2l0IHdpbGwgbW9zdCBsaWtlbHkgaW5mbHVlbmNlIHNvbWVvbmUgdG8gbm90IGNhbmNlbCB0aGVpciByZXNlcnZhdGlvbi4KCmBgYHtyfQpob3RlbF9sYXNzb190ZXN0IDwtIGhvdGVsX2xhc3NvX2ZpbmFsX3dvcmtmbG93ICU+JSAKICBsYXN0X2ZpdChob3RlbF9zcGxpdCkKCmhvdGVsX2xhc3NvX3Rlc3QgJT4lIAogIGNvbGxlY3RfbWV0cmljcygpCmBgYAoKKiBUaGUgYWNjdXJhY3kgZXN0aW1hdGUgZm9yIHRoZSB0ZXN0IGRhdGEgaXMgc2xpZ2h0bHkgaGlnaGVyIHRoYW4gZm9yIHRoZSBjcm9zcy12YWxpZGF0ZWQgZGF0YS4KCmBgYHtyfQpwcmVkcyA8LSBjb2xsZWN0X3ByZWRpY3Rpb25zKGhvdGVsX2xhc3NvX3Rlc3QpCgpwcmVkcyAlPiUgCiAgY29uZl9tYXQoLnByZWRfY2xhc3MsIGlzX2NhbmNlbGVkKQpgYGAKCiogU2Vuc2l0aXZpdHk6ICRcZnJhY3szNDQ5Mn17MzQ0OTIrODAwNH0gPSAwLjgxMTY1JAoqIFNwZWNpZmljaXR5OiAkXGZyYWN7MTQwMzJ9ezE0MDMyICsgMzE2NX0gPSAwLjgxNTk1NiQKCmBgYHtyfQpwcmVkcyAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gLnByZWRfMSwgZmlsbCA9IGlzX2NhbmNlbGVkKSkgKwogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSwgY29sb3IgPSBOQSkKYGBgCgoqIEZvciBhbiBhY2N1cmFjeSBjbG9zZSB0byAxLCB0aGUgcGxvdCB3b3VsZCBoYXZlIGFsbW9zdCBubyBvdmVybGFwIGF0IGFsbCwgYXMgdGhlIHRydWUgcG9zaXRpdmUgYW5kIHRydWUgbmVnYXRpdmUgcmF0ZXMgd291bGQgZWFjaCBiZSBjbG9zZSB0byAxLgoqIElmIHdlIHdhbnQgdG8gaGF2ZSBhIGhpZ2hlciB0cnVlIHBvc2l0aXZlIHJhdGUsIHdlIHNob3VsZCBtYWtlIHRoZSBjdXRvZmYgaGlnaGVyIHRoYW4gMC41LCBhcyBvbmx5IGluY2x1ZGluZyBwcmVkaWN0aW9ucyB0aGF0IGFyZSBtb3JlIGxpa2VseSB0byBiZSBjYW5jZWxlZCB3b3VsZCBmaWx0ZXIgb3V0IHByZWRpY2l0aW9ucyB0aGF0IHdlcmUgc29tZXdoYXQgbGlrZWx5IChpLmUuIDUwJS02MCUgbGlrZWx5KSB0byBiZSBjYW5jZWxlZCB0aGF0IGVuZGVkIHVwIG5vdCBiZWluZyBjYW5jZWxlZC4gCiogSWYgdGhlIHRydWUgcG9zaXRpdmUgcmF0ZSBpbmNyZWFzZXMsIHRoZSB0cnVlIG5lZ2F0aXZlIHJhdGUgc2hvdWxkIGluY3JlYXNlIGFzIHdlbGwsIGJlY2F1c2UgYnkgaW5jcmVhc2luZyB0aGUgdHJ1ZSBwb3NpdGl2ZSByYXRlLCB3ZSBhcmUgZWxpbWluYXRpbmcgaW5jb3JyZWN0IHByZWRpY3Rpb25zLiBFbGltaW5hdGluZyBpbmNvcnJlY3QgcHJlZGljdGlvbnMgd2lsbCBkcml2ZSBkb3duIHRoZSBmYWxzZSBuZWdhdGl2ZSByYXRlLgoKCihAKSBMZXTigJlzIHNheSB0aGF0IHRoaXMgbW9kZWwgaXMgZ29pbmcgdG8gYmUgYXBwbGllZCB0byBib29raW5ncyAxNCBkYXlzIGluIGFkdmFuY2Ugb2YgdGhlaXIgYXJyaXZhbCBhdCBlYWNoIGhvdGVsLCBhbmQgc29tZW9uZSB3aG8gd29ya3MgZm9yIHRoZSBob3RlbCB3aWxsIG1ha2UgYSBwaG9uZSBjYWxsIHRvIHRoZSBwZXJzb24gd2hvIG1hZGUgdGhlIGJvb2tpbmcuIER1cmluZyB0aGlzIHBob25lIGNhbGwsIHRoZXkgd2lsbCB0cnkgdG8gYXNzdXJlIHRoYXQgdGhlIHBlcnNvbiB3aWxsIGJlIGtlZXBpbmcgdGhlaXIgcmVzZXJ2YXRpb24gb3IgdGhhdCB0aGV5IHdpbGwgYmUgY2FuY2VsaW5nIGluIHdoaWNoIGNhc2UgdGhleSBjYW4gZG8gdGhhdCBub3cgYW5kIHN0aWxsIGhhdmUgdGltZSB0byBmaWxsIHRoZSByb29tLiBIb3cgc2hvdWxkIHRoZSBob3RlbCBnbyBhYm91dCBkZWNpZGluZyB3aG8gdG8gY2FsbD8gSG93IGNvdWxkIHRoZXkgbWVhc3VyZSB3aGV0aGVyIGl0IHdhcyB3b3J0aCB0aGUgZWZmb3J0IHRvIGRvIHRoZSBjYWxsaW5nPyBDYW4geW91IHRoaW5rIG9mIGFub3RoZXIgd2F5IHRoZXkgbWlnaHQgdXNlIHRoZSBtb2RlbD8KCiogVGhlIGhvdGVsIHNob3VsZCBmaXJzdCBjYWxsIHBlb3BsZSB3aG8gYXJlIGluIHJvb20gdHlwZSBQLCBhcyByb29tIHR5cGUgUCBpcyB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGUgd2hlbiBpdCBjb21lcyB0byBwcmVkaWN0aW5nIHdoZXRoZXIgYSByZXNlcnZhdGlvbiB3aWxsIGJlIGNhbmNlbGVkIG9yIG5vdC4gVGhlIGhvdGVsIHNob3VsZCBhbHNvIGxvb2sgYXQgdGhlIHR5cGUgb2YgZGVwb3NpdCB0aGF0IHRoZSByZXNlcnZhdGlvbiBtYWtlciBwdXQgZG93biwgYXMgdGhhdCBpcyBhbHNvIHZlcnkgcHJlZGljdGl2ZSBvZiB3aGV0aGVyIHRoZSByZXNlcnZhdGlvbiB3aWxsIGJlIGNhbmNlbGVkIG9yIG5vdC4gVGhlIHdheSB0aGF0IHRoZXkgY291bGQgbWVhc3VyZSB3aGV0aGVyIHRoZSBjYWxscyB3ZXJlIHdvcnRoIGl0IG9yIG5vdCBjb3VsZCBiZSBieSBsb29raW5nIGF0IGhvdyBtYW55IGNhbmNlbGxhdGlvbnMgd2VyZSBkaXNjb3ZlcmVkIGJhc2VkIG9uIHRoZSBjYWxscyB2ZXJzdXMgdGhlIHRvdGFsIG51bWJlciBvZiBjYW5jZWxsYXRpb25zLiBUaGV5IGNvdWxkIGFsc28gb2JzZXJ2ZSBob3cgbWFueSByZXNlcnZhdGlvbnMgd2VyZSBtYWRlIGZvciB0aGUgcm9vbXMgYWZ0ZXIgdGhleSB3ZXJlIGNhbmNlbGVkIGFzIGEgcmVzdWx0IG9mIHRoZSBjYWxscy4gQW5vdGhlciB3YXkgdGhhdCB0aGV5IGNvdWxkIHVzZSB0aGUgbW9kZWwgaXMgYnkgbG9va2luZyBhdCB0aGUgcm9vbXMgdGhhdCBhcmUgY2FuY2VsZWQgdGhlIG1vc3QgYW5kIG1ha2luZyBkZXBvc2l0cyBvbiB0aG9zZSByb29tcyBub24tcmVmdW5kYWJsZS4KCihAKSBIb3cgbWlnaHQgeW91IGdvIGFib3V0IHF1ZXN0aW9uaW5nIGFuZCBldmFsdWF0aW5nIHRoZSBtb2RlbCBpbiB0ZXJtcyBvZiBmYWlybmVzcz8gQXJlIHRoZXJlIGFueSBxdWVzdGlvbnMgeW91IHdvdWxkIGxpa2UgdG8gYXNrIG9mIHRoZSBwZW9wbGUgd2hvIGNvbGxlY3RlZCB0aGUgZGF0YT8KCiogSW4gdGVybXMgb2YgZmFpcm5lc3MsIHdlIG1pZ2h0IHdhbnQgdG8gZGlzY3VzcyB0aGUgcG90ZW50aWFsIGlzc3VlcyB3aXRoIHdobyB3ZSBhcmUgZXZhbHVhdGluZy4gV2l0aCByZWdhcmRzIHRvIHRoZSBpbmRpdmlkdWFsLCBpdCBpcyBwb3NzaWJsZSB0aGF0IHdlIGFyZSBvbmx5IGxvb2tpbmcgYXQgd2VhbHRoeSBwZW9wbGUgd2hvIGNhbiBhZmZvcmQgdG8gZ28gb24gdmFjYXRpb24gYW5kIHN0YXkgYXQgYSBob3RlbCBvciBhIHJlc29ydC4gV2l0aCByZWdhcmRzIHRvIHRoZSBncm91cCByZXNlcnZhdGlvbiB0eXBlLCBpdCBpcyBwb3NzaWJsZSB0aGF0IHRoZSBtYWpvcml0eSBhcmUgZWl0aGVyIG11bHRpcGxlIHdlYWx0aHkgZnJpZW5kcywgb3IgZmFtaWxpZXMgd2hvIGFyZSBhYmxlIHRvIGFmZm9yZCBnb2luZyBvbiB2YWNhdGlvbi4gVGhpcyBpcyBhIHNpdHVhdGlvbiB3aGVyZSBkZW1vZ3JhcGhpY3MgYXJlIGltcG9ydGFudCwgYW5kIEkgd291bGQgd2FudCB0byBhc2sgdGhlIHJlc2VhcmNoZXJzIGFib3V0IHRoZSBkZW1vZ3JhcGhpY3Mgb2YgdGhlIHBlb3BsZSB3aG8gd2VyZSBtYWtpbmcgdGhlIHJlc2VydmF0aW9ucywgYXMgYnkgaW5jbHVkaW5nIG1vc3RseSB3ZWFsdGh5IHBlb3BsZSwgdGhlIHJlc2VhcmNoZXJzIHdvdWxkIG5vdCBiZSB1bmRlcnN0YW5kaW5nIHRoZSB0cnVlIHRyZW5kcyBvZiB0aGUgcG9wdWxhdGlvbi4KCgoKIyMgQmlhcyBhbmQgRmFpcm5lc3MgUmVmbGVjdGlvbgoKKiBUaHJvdWdob3V0IHdhdGNoaW5nIHRoaXMgbGVjdHVyZSwgSSB3YXMgbm90IG5lY2Vzc2FyaWx5IHN1cnByaXNlZCBieSBhbnl0aGluZywgYnV0IEkgZGlkIGZpbmQgaXQgaW50ZXJlc3RpbmcgaG93IGEgbG90IG9mIHRoZSBiaWFzZXMgdXNlZCByYWNlIGFzIGFuIGV4YW1wbGUuIEkgZGlkIG5vdCByZWFsaXplIGhvdyBtdWNoIG9mIGFuIGlzc3VlIHJhY2lhbCBiaWFzIHdhcyBpbiBkYXRhIHNjaWVuY2UsIGFuZCBob3cgbGFyZ2Ugb3JnYW5pemF0aW9ucyBzdWNoIGFzIEZhY2Vib29rLCBBbWF6b24sIGFuZCBJQk0gYXJlIGNvbnRyaWJ1dGluZyB0byBpdCBzbyBtdWNoLiBUaGlzIGxlY3R1cmUgYWxzbyBoZWxwZWQgbWUgdW5kZXJzdGFuZCB3aHkgaXQgaXMgc28gaW1wb3J0YW50IHRvIHBheSBhdHRlbnRpb24gdG8gYmlhcyBpbiBtYWNoaW5lIGxlYXJuaW5nIGFuZCB3aGVuIHdyaXRpbmcgYWxnb3JpdGhtcyBhbmQgSSBjYW1lIHRvIHRoaXMgY29uY2x1c2lvbjogaWRlbnRpZnlpbmcgYmlhc2VzIGNhbiBoZWxwIHBlb3BsZSBmaWd1cmUgb3V0IHdoZW4gdGhlaXIgYWxnb3JpdGhtIGlzICoqd3JvbmcqKi4gSWRlbnRpZnlpbmcgdGhlIHBvdGVudGlhbCBiaWFzZXMgaW4gYW4gYWxnb3JpdGhtIGNhbiBoZWxwIGEgcmVzZWFyY2hlciBmaW5kIHdoYXQsIG9yIHdobywgdGhlaXIgYWxnb3JpdGhtIGlzIG9taXR0aW5nLgoKCgoKCgoKCg==